String token = generateOTP(); String userName = "admin"; String password = "admin"; UserCredential credential = new OTPCredential(userName, password, token);
This section describes how to authenticate users using a username, password and a Time-Based One-Time Password (TOTP) as credentials.
This authentication type is provided by the org.picketbox.core.authentication.impl.OTPAuthenticationMechanism.
You do not need any specific configuration to use this mechanism, it is already configured when you start PicketBox.
This mechanism supports the following credential:
org.picketbox.core.authentication.credential.OTPCredential
The code bellow demonstrates how to create this credential type.
String token = generateOTP(); String userName = "admin"; String password = "admin"; UserCredential credential = new OTPCredential(userName, password, token);
To generate TOTP tokens programatically you use the org.picketbox.core.util.TimeBasedOTP utility class.
String secretKey = "change_me"; String totp = TimeBasedOTP.generateTOTP(secretKey, NUMBER_OF_DIGITS); // now you can authenticate the user using the generated token
Beside the secret key, you must specify the token length (see the NUMBER_OF_DIGITS above). Usually, you will use 6 digits.
If you want to generate tokens on your phone you can use the Google Authenticator. Check the documentation for details about how installing and configuring your tokens.
The only thing you should be aware is about time syncronization between your phone and the server where you application is running. To help you sync your devices, you can use the TOTP Debugger. This tools helps you to find problems related with time syncronization between your phone, the google servers and the server where your application is running.
The OTP authentication mechanism must know where the secret key is stored to validate tokens. That said, before authenticating an user you need to provide his secret key and store it as an attribute.
/** * <p>Generates a secret key for an user if none is defined. The secret key should be stored as an attribute to allow the authentication mechanism retrieve it and perform the token validation.</p> */ private String generateOTP(User user) throws GeneralSecurityException { String secretKey = user.getAttribute("serial"); if (secretKey == null) { //Generate a random number and use as the secret key secretKey = UUID.randomUUID().toString(); secretKey = secretKey.replace('-', 'c'); //Just pick the first 10 characters secretKey = secretKey.substring(0, 10); user.setAttribute("serial", secretKey); } return TimeBasedOTP.generateTOTP(secretKey, NUMBER_OF_DIGITS); }
The name of the attribute where the secret key is stored must be "serial". This name will be used by the OTP authentication mechanism to locate the attribute and get the secret key to perform the authentication.
Another important point regarding validation is how a specific token is considered valid given the time interval in which it was generated. During the validation the following rules are applied:
Check if the provided token matches a token generated by the server using the current interval or time. If so, the token is considered valid.
Check if the provided token matches a token generated considering the previous interval or time.
Check if the provided token matches a token generated considering the next interval or time.
The interval has a important hole when dealing with TOTP tokens. The time of the interval impacts directly how the token is generated and validated. That said, PicketBox uses by default an interval of 30 seconds. This is the window to consider if a token is valid or not.
/** * <p>Token number of digits.</p> */ private static final int NUMBER_OF_DIGITS = 6; /** * <p> * Tests if the authentication performs successfully when provided a valid {@link OTPCredential}. * </p> * @throws Exception * * @throws AuthenticationException */ @Test public void testSuccessfulAuthentication() throws Exception { PicketBoxManager picketBoxManager = createManager(); // gets the identity manager instance IdentityManager identityManager = picketBoxManager.getIdentityManager(); // load the user from the identity store User adminUser = identityManager.getUser("admin"); // creates new authenticating context UserContext authenticatingUser = new UserContext(); String userName = "admin"; String password = "admin"; // generates the TOTP token String totp = generateOTP(adminUser); // creates the OTP credential UserCredential credential = new OTPCredential(userName, password, totp); // populates the authenticating context authenticatingUser.setCredential(credential); // let's authenticate the user UserContext authenticatedUser = picketBoxManager.authenticate(authenticatingUser); assertNotNull(authenticatedUser); assertTrue(authenticatedUser.isAuthenticated()); } /** * <p>Generates a secret key for an user if none is defined. The secret key should be stored as an attribute to allow the authentication mechanism retrieve it and perform the token validation.</p> */ private String generateOTP(User user) throws GeneralSecurityException { String secretKey = user.getAttribute("serial"); if (secretKey == null) { //Generate a random number and use as the secret key secretKey = UUID.randomUUID().toString(); secretKey = secretKey.replace('-', 'c'); //Just pick the first 10 characters secretKey = secretKey.substring(0, 10); user.setAttribute("serial", secretKey); } return TimeBasedOTP.generateTOTP(secretKey, NUMBER_OF_DIGITS); }